#include "ShadowMap.h"

//based on http://www.fabiensanglard.net/shadowmapping/index.php
ShadowMap::ShadowMap(int size) {
    this->size = size;

    //generate and bind a framebuffer
    glGenFramebuffersEXT(1, &frameBuffer);
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frameBuffer);

    //generate and bind a renderbuffer (for the depth)
    glGenRenderbuffersEXT(1, &depthBuffer);
    glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthBuffer);

    //disable color rendering for these buffers
    glDrawBuffer(GL_NONE);
    glReadBuffer(GL_NONE); 

    //generate and bind and depth texture
    glGenTextures(1, &depthTexture);
    glBindTexture(GL_TEXTURE_2D, depthTexture);

    //disable interpolation of depth texture
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    // This is to allow usage of shadow2DProj function in the shader
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
	glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY); 

    //disable depth texture wraparound
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP ); 

    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, size, size, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);

    //assign the depth buffer the appropriate size in storage
    glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, size, size);

    //set the frameBuffer's depth to the depth buffer
    glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthBuffer);

    //attach the texture to the renderBuffer
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depthTexture, 0);

    //reset bindings
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
    glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
    glBindTexture(GL_TEXTURE_2D, 0);
}

void ShadowMap::preRender(double x, double y, double z) {
    //standard rendering
    glUseProgram(0);

    //set this shadow map's framebuffer as the target
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frameBuffer);

    //set viewport to the map's size
    glViewport(0, 0, size, size);

    //reset depth values
    glClear(GL_DEPTH_BUFFER_BIT);

    //disable color rendering
    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 

    //use back faces as the depth value
    glCullFace(GL_FRONT);

    // set up projection/modelview
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	
    glViewport(0, 0, size, size);
    glFrustum(-1, 1, -1, 1, 1, 1000);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(x, y, z, 
		      0.0, 0.0, 0.0,
			  0.0, 1.0, 0.0);
}

void ShadowMap::postRender(GLenum texture) {
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 
    glCullFace(GL_FRONT);

    setTextureMatrix(texture);
}

void ShadowMap::setTextureMatrix(GLenum texture) {
    GLdouble modelview[16], projection[16];
    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
	glGetDoublev(GL_PROJECTION_MATRIX, projection);

    glMatrixMode(GL_TEXTURE);
    glActiveTexture(texture);

    glLoadMatrixd(bias);
    glMultMatrixd(projection);
	glMultMatrixd(modelview);

    //reset matrix mode
    glMatrixMode(GL_MODELVIEW);
}
